package org.zjvis.datascience.service.graph;

import org.apache.tinkerpop.gremlin.driver.*;
import org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0;
import org.apache.tinkerpop.gremlin.process.traversal.Path;

import java.sql.Types;

import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoMapper;
import org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.zjvis.datascience.common.graph.util.GraphUtil;
import org.zjvis.datascience.common.vo.graph.*;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description JanusGraphService
 * @date 2021-12-29
 */
@Deprecated
public class JanusGraphService {
    private final static Logger logger = LoggerFactory.getLogger(JanusGraphService.class);
    private final boolean DEBUG = true;

    private Cluster cluster;
    private Client client;
    private Map<String, String> templateMap;
    private String SERVER_HOSTNAME = "10.5.24.112";
    private int SERVER_PORT = 8182;
    private String STORAGE_BACKEND = "cql";
    private String STORAGE_HOSTNAME = "127.0.0.1";
    private String SEARCH_BACKEND = "elasticsearch";
    private String SEARCH_HOSTNAME = "127.0.0.1";

    @PostConstruct
    private void init() {
        try {
            long start = System.currentTimeMillis();
            connect();
            long end = System.currentTimeMillis();
            logger.info("JanusGraphService connect in {} ms.", (end - start));
        } catch (Exception e) {
//            throw new RuntimeException(e);
        }
        if (!DEBUG) {
        templateMap = initTemplateMap();
        getTemplateConfiguration();
        }
    }

    public void connect() throws ConfigurationException {
        logger.info("Connecting to JanusGraph Server...");
        try {
            GryoMapper.Builder mapper = GryoMapper.build().addRegistry(JanusGraphIoRegistry.instance());
            cluster = Cluster.build()
                    .serializer(new GryoMessageSerializerV3d0(mapper))
                    .addContactPoint(SERVER_HOSTNAME)
                    .port(SERVER_PORT)
                    .create();
            client = cluster.connect().init();
        } catch (Exception e) {
//            throw new ConfigurationException(e);
        }
            logger.info("JanusGraph Server connect success");
    }

    public void getTemplateConfiguration() {
        String req = "ConfiguredGraphFactory.getTemplateConfiguration();";
        List<Result> resultList = this.submit(req);
        List<Object> results = resultList.stream().map(Result::getObject).collect(Collectors.toList());
        if (results.get(0) != null) {
            removeTemplateConfiguration();
        }
        createTemplateConfiguration();
    }

    public void createTemplateConfiguration() {
        Map<String,Object> params = new HashMap<>();
        params.put("map", templateMap);
        String req = "ConfiguredGraphFactory.createTemplateConfiguration(new MapConfiguration(map));";
        this.submit(req, params);
    }

    public void updateTemplateConfiguration() {
        Map<String,Object> params = new HashMap<>();
        params.put("map", templateMap);
        String req = "ConfiguredGraphFactory.updateTemplateConfiguration(new MapConfiguration(map));";
        this.submit(req, params);
    }

    public void removeTemplateConfiguration() {
        String req = "ConfiguredGraphFactory.removeTemplateConfiguration();";
        this.submit(req);
    }

    public void createConfiguredGraph(String graphName) {
        String req = String.format("graph = ConfiguredGraphFactory.create('%s'); ConfiguredGraphFactory.close('%s'); " +
                "ConfiguredGraphFactory.getTemplateConfiguration();", graphName, graphName);
        client.submit(req);
    }

    public void closeConfiguredGraph(String graphName) {
        String req = String.format("ConfiguredGraphFactory.close('%s'); ConfiguredGraphFactory.getTemplateConfiguration();", graphName);
        client.submit(req);
    }

    public void openConfiguredGraph(String graphName) {
        String req = String.format("try{graph = ConfiguredGraphFactory.open('%s'); ConfiguredGraphFactory.getTemplateConfiguration();} " +
                        "catch(NullPointerException e) {graph = ConfiguredGraphFactory.create('%s'); ConfiguredGraphFactory.getTemplateConfiguration();};",
                graphName, graphName);
        client.submit(req);
    }

    public void deleteConfiguredGraph(String graphName) {
        //需要drop的图必须处于开启状态，否则无法正常删除
        String req = String.format("ConfiguredGraphFactory.open('%s');ConfiguredGraphFactory.drop('%s');", graphName, graphName);
        client.submit(req);
    }

    public void queryInfo(String graphName) {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("graph = ConfiguredGraphFactory.open('%s');g=graph.traversal();g.V().toList();", graphName));
        String req = sb.toString();
        List<Result> results = this.submit(req);
        List<Vertex> vertices = results.stream().map(Result::getVertex).collect(Collectors.toList());
        logger.info("vertex: " + vertices.toString());

        sb = new StringBuilder();
        sb.append(String.format("graph = ConfiguredGraphFactory.open('%s');g=graph.traversal();g.E().toList();", graphName));
        req = sb.toString();
        results = this.submit(req);
        List<Edge> edges = results.stream().map(Result::getEdge).collect(Collectors.toList());
        logger.info("edge: " + edges.toString());
    }

    public Map<String, String> initTemplateMap() {
        Map<String, String> map = new HashMap<>();
        map.put("storage.backend",STORAGE_BACKEND);
        map.put("storage.hostname",STORAGE_HOSTNAME);
        map.put("search.backend",SEARCH_BACKEND);
        map.put("search.hostname",SEARCH_HOSTNAME);
        return map;
    }

    public List<Result> submit(String req) {
        try {
            long start = System.currentTimeMillis();
            List<Result> results = client.submit(req).all().get();
            long end = System.currentTimeMillis();
            logger.info("submit exec in {} ms.  submit={}", (end - start), req);
            return results;
        } catch (Exception e) {
            logger.error("submit error: error = {},  sumit={}", e, req);
            return new ArrayList<>();
        }
    }

    public List<Result> submit(String req, Map<String, Object> params) {
        try {
            long start = System.currentTimeMillis();
            List<Result> results = client.submit(req, params).all().get();
            long end = System.currentTimeMillis();
            logger.info("submit exec in {} ms.  submit={},  params={}", (end - start), req, params);
            return results;
        } catch (Exception e) {
            logger.error("submit error: error = {},  sumit={},  params={}", e, req, params);
            return new ArrayList<>();
        }
    }

    @PreDestroy
    public void close() {
        if (cluster != null){
            cluster.close();

        }
        if (client != null) {
            client.close();
        }
        cluster = null;
        client = null;
        logger.info("JanusGraphService disconnect success");

    }

    public String createSchema(List<CategoryVO> categoryVOList, List<EdgeVO> edgeVOList) {
        StringBuilder s = new StringBuilder();
        s.append("JanusGraphManagement management = graph.openManagement(); ");
        s.append("boolean created = false; ");

        // naive check if the schema was previously created
        s.append(
                "if (management.getRelationTypes(RelationType.class).iterator().hasNext()) { management.rollback(); created = false; } else { ");

        s.append(GraphUtil.getPropertyRequest("refId", Types.VARCHAR));
        s.append(GraphUtil.getCompositeIndexRequest("refId"));
        for (CategoryVO category: categoryVOList) {
            s.append(GraphUtil.getCategoryLabelRequest(category.getOriginLabel().getName()));
            s.append(GraphUtil.getPropertyRequest(category.getKeyAttr().getName(), category.getKeyAttr().getType()));
            //为主属性建立索引
            s.append(GraphUtil.getCompositeIndexRequest(category.getKeyAttr().getName()));
            for (CategoryAttrVO attr: category.getAttrs()) {
                s.append(GraphUtil.getPropertyRequest(attr.getName(), attr.getType()));
            }
        }
        for (EdgeVO edge: edgeVOList) {
            s.append(GraphUtil.getEdgeLabelRequest(edge.getLabel()));
            for (CategoryAttrVO attr: edge.getAttrs()) {
                s.append(GraphUtil.getPropertyRequest(attr.getName(), attr.getType()));
            }
        }
        s.append("management.commit(); created = true; };");
        return s.toString();
    }

    public String createNode(NodeVO node, CategoryVO category) {
        Map<String, Object> propertyMap = new HashMap<>();
        for (AttrVO attr: node.getAttrs()) {
            propertyMap.put(attr.getKey(), GraphUtil.parsePropertyValue(attr.getValue().toString(), attr.getType()));
        }
        return GraphUtil.getNodeRequest(category.getOriginLabel().getName(), node.getId(), propertyMap);
    }

    public String createLink(LinkVO link, EdgeVO edge) {
        StringBuilder s = new StringBuilder();
        if (edge.getStyle()==null || edge.getStyle().getBoolean("endArrow")){
            s.append(GraphUtil.getLinkRequest(
                    edge.getLabel(),
                    link.getSource(),
                    link.getTarget(),
                    link.getId()
            ));
        } else {
            //无向边建两次
            s.append(GraphUtil.getLinkRequest(
                    edge.getLabel(),
                    link.getSource(),
                    link.getTarget(),
                    link.getId() + "_out"
            ));
            s.append(GraphUtil.getLinkRequest(
                    edge.getLabel(),
                    link.getTarget(),
                    link.getSource(),
                    link.getId() + "_in"
            ));
        }
        return s.toString();
    }

    public void createGraphAll(String graphName,
                               List<CategoryVO> categoryVOList, List<EdgeVO> edgeVOList,
                               Map<String, CategoryVO> cIdMap, Map<String, EdgeVO> eIdMap,
                               List<NodeVO> nodeVOList, List<LinkVO> linkVOList) {
        logger.info("createGraphAll janusGraphName: " + graphName);
        StringBuilder s = new StringBuilder();
        s.append(String.format(
                "try{graph = ConfiguredGraphFactory.open('%s'); ConfiguredGraphFactory.getTemplateConfiguration();} " +
                        "catch(NullPointerException e) {graph = ConfiguredGraphFactory.create('%s'); ConfiguredGraphFactory.getTemplateConfiguration();};",
                graphName, graphName));
        this.submit(s.toString());

        s = new StringBuilder();
        s.append(String.format("graph = ConfiguredGraphFactory.open('%s');", graphName));
        if (categoryVOList.size() != 0 ) {
            s.append(createSchema(categoryVOList, edgeVOList));
        }
        this.submit(s.toString());

        //分batch入库
        String handle =  String.format("g = ConfiguredGraphFactory.open('%s').traversal();", graphName);
        String drop = "g.V().drop().iterate();";
        String commit = "g.tx().commit();";

        StringBuilder sNode = new StringBuilder();
        boolean firstflag = true;
        for (NodeVO node: nodeVOList) {
            String req = createNode(node, cIdMap.get(node.getCategoryId()));
            sNode.append(req);
            if (sNode.length() >= 30000) {
                if (firstflag) {
                    this.submit(handle + drop + sNode.toString() + commit);
                    firstflag = false;
                } else {
                    this.submit(handle + sNode.toString() + commit);
                }
                sNode = new StringBuilder();
            }
        }
        if (sNode.length() > 0) {
            if (firstflag) {
                this.submit(handle + drop + sNode.toString() + commit);
            } else {
                this.submit(handle + sNode.toString() + commit);
            }
        }

        StringBuilder sLink = new StringBuilder();
        for (LinkVO link: linkVOList) {
            String req = createLink(link, eIdMap.get(link.getEdgeId()));
            sLink.append(req);
            if (sLink.length() >= 30000) {
                this.submit(handle + sLink.toString() + commit);
                sLink = new StringBuilder();
            }
        }
        if (sLink.length() > 0) {
            this.submit(handle + sLink.toString() + commit);
        }
    }

    public List<Path> shortestPathBetween2Vertex(String graphName, String srcId, String tarId, Integer maxStep) {
        String req = GraphUtil.getTraversalRequest() + GraphUtil.getShortestPathRequest();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("src", srcId);
        params.put("tar", tarId);
        params.put("maxStep", maxStep);
        List<Result> results = this.submit(req, params);
        List<Path> shortestPath = results.stream().map(Result::getPath).collect(Collectors.toList());
        List<Path> validPath = new ArrayList<>();
        for (Path path: shortestPath) {
            if (path.get(path.size() - 1).equals(tarId)) {
                validPath.add(path);
            }
        }
        return validPath;
    }

    public List<Path> allPathBetween2Vertex(String graphName, String srcId, String tarId, Integer maxStep) {
        String req = GraphUtil.getTraversalRequest() + GraphUtil.getAllPathRequest();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("src", srcId);
        params.put("tar", tarId);
        params.put("maxStep", maxStep);
        List<Result> results = this.submit(req, params);
        List<Path> allPath = results.stream().map(Result::getPath).collect(Collectors.toList());
        List<Path> validPath = new ArrayList<>();
        for (Path p: allPath) {
            List<Object>  o = p.objects();
            String endId = (String) o.get(o.size() - 1);
            if (endId.equals(tarId)) {
                validPath.add(p);
            }
        }
        return validPath;
    }

    public Map<String, List<String>> PeerPressure(String graphName, List<String> refIdList, Integer maxIter) {
        String req = GraphUtil.getTraversalRequest() + GraphUtil.getPeerPressureRequest();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("refIdList", refIdList);
        params.put("maxIter", maxIter);
        List<Result> results = this.submit(req, params);
        Map<String, List<String>> clusterResult = new HashMap<>();
        if (results.size() == 0) {
            return clusterResult;
        }
        Map<Long, List<String>> tmp = (Map<Long, List<String>>) results.get(0).getObject();
        List<List<String>> clusterIds = new ArrayList<>(tmp.values());


        for (int i=0; i < clusterIds.size(); i++) {
            clusterResult.put(String.format("cluster_%d", i), clusterIds.get(i));
        }
        return clusterResult;
    }

    public void dropNode(String graphName, String refId) {
        String req = GraphUtil.getTraversalRequest() + GraphUtil.getDropNodeRequest();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("refId", refId);
        this.submit(req, params);
    }

    public void dropLink(String graphName, String refId) {
        String req = GraphUtil.getTraversalRequest() + GraphUtil.getDropLinkRequest();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("refId", refId);
        this.submit(req, params);
    }

    public void batchDropNodesAndLinks(String graphName, List<String> refIdListNode, List<String> refIdListLink) {
        String req = GraphUtil.getTraversalRequest() + GraphUtil.getBatchDropNodesAndLinksRequest();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("refIdListNode", refIdListNode);
        params.put("refIdListLink", refIdListLink);
        this.submit(req, params);
    }

    public List<String> search4attrNames(String graphName, List<String> attrNames, String predicate) {
        StringBuilder s = new StringBuilder();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        for (int i =0; i<attrNames.size(); i++) {
            params.put("property" + i, attrNames.get(i));
            params.put("value" + i, predicate);
        }
        s.append(GraphUtil.getTraversalRequest());
        s.append(GraphUtil.getSearch4attrNamesRequest(attrNames.size()));
        String req = s.toString();
        List<Result> results = this.submit(req, params);
        List<String> refIds = results.stream().map(Result::getString).collect(Collectors.toList());
        return refIds;
    }

    public List<String> adjacentWithStep(String graphName, List<String> refIds, Integer maxStep) {
        StringBuilder s = new StringBuilder();
        Map<String,Object> params = new HashMap<>();
        params.put("graphName", graphName);
        params.put("refIdList", refIds);
        params.put("maxStep", maxStep);
        s.append(GraphUtil.getTraversalRequest());
        s.append(GraphUtil.getAdjacentWithStepRequest());
        String req = s.toString();
        List<Result> results = this.submit(req, params);
        List<String> adjacentRefIds = results.stream().map(Result::getString).collect(Collectors.toList());
        return adjacentRefIds;
    }
}
